﻿#include "pch.h"
#include "qgp/qgach.h"
#include "qg_blits.h"

#ifndef QG_NO_BMP

namespace _img
{
#include "extend/sbpackenter.h"
	struct FileFmtBMP
	{
		kushort			Id;
		kuint			FileSize;
		kuint			Reserved;
		kuint			BitmapDataOffset;
		kuint			BitmapHeaderSize;
		kuint			Width;
		kuint			Height;
		kushort			Planes;
		kushort			BPP;
		kuint			Compression;
		kuint			BitmapDataSize;
		kuint			PixelPerMeterX;
		kuint			PixelPerMeterY;
		kuint			Colors;
		kuint			ImportantColors;
	} K_PACKED;
#include "extend/sbpackleave.h"

	//
	class ImageReaderBmp : public qbFsReader, public tpl::MemAlloc<>
	{
	public:
		static kbyte* Dec8BitRle(kbyte* data, kint size, kint width, kint height, kint lpad)
		{
			kbyte* ptr = data;
			kbyte* newdata = k_new((width + lpad) * height, kbyte);
			kbyte* dst = newdata;
			kbyte* end = newdata + (width + lpad) * height;
			kint i, line = 0;
			kint cnt, readadd;
			kbyte cc;

			while ((kint)(data - ptr) < size && dst < end)
			{
				if (!*ptr)
				{
					++ptr;

					switch (*ptr)
					{
						case 0:
							ptr++;
							line++;
							dst = newdata + (line * (width + lpad));
							break;

						case 1:
							k_delete(data);
							return newdata;

						case 2:
							ptr++;
							dst += (kbyte)(*ptr);
							ptr++;
							dst += ((kbyte)(*ptr)) * (width + lpad);
							ptr++;
							break;

						default:
							cnt = (kbyte)(*ptr);
							ptr++;
							readadd = (2 - (cnt % 2)) % 2;

							for (i = 0; i < cnt; i++)
								*dst++ = *ptr++;

							for (i = 0; i < readadd; i++)
								ptr++;

							break;
					}
				}
				else
				{
					cnt = (kbyte)(*ptr);
					ptr++;
					cc = *ptr;
					ptr++;

					for (i = 0; i < cnt; i++)
						*dst++ = cc;
				}
			}

			k_delete(data);

			return newdata;
		}

		static kbyte* Dec4BitRle(kbyte* data, kint size, kint width, kint height, kint lpad)
		{
			kint lwidth = (width + 1) / 2 + lpad;
			kbyte* ptr = data;
			kbyte* newdata = k_new(lwidth * height, kbyte);
			kbyte* dst = newdata;
			kbyte* end = newdata + lwidth * height;
			kint i, z, line = 0, shift = 4;
			kint cnt, readadd, readshift;
			kint cc1, cc2;

			while ((kint)(data - ptr) < size && dst < end)
			{
				if (!*ptr)
				{
					++ptr;

					switch (*ptr)
					{
						case 0:
							ptr++;
							line++;
							dst = newdata + (line * lwidth);
							shift = 4;
							break;

						case 1:
							k_delete(data);
							return newdata;

						case 2:
							ptr++;
							i = (kbyte)(*ptr);
							ptr++;
							z = (kbyte)(*ptr);
							ptr++;
							dst += i / 2 + z * lwidth;
							shift = i % 2 == 0 ? 4 : 0;
							break;

						default:
							cnt = (kbyte)(*ptr);
							ptr++;
							readadd = (2 - (cnt % 2)) % 2;
							readshift = 4;

							for (i = 0; i < cnt; i++)
							{
								kint cc = (((kbyte)(*ptr)) >> readshift) & 0x0F;
								kbyte mask;

								readshift -= 4;

								if (readshift < 0)
								{
									++*ptr;
									readshift = 4;
								}

								mask = 0x0F << shift;
								*dst = (*dst & (~mask)) | ((cc << shift)&mask);

								shift -= 4;

								if (shift < 0)
								{
									shift = 4;
									dst++;
								}
							}

							for (i = 0; i < readadd; i++)
								ptr++;

							break;
					}
				}
				else
				{
					cnt = (kbyte)* ptr;
					ptr++;
					cc1 = cc2 = (kbyte)* ptr;
					ptr++;

					cc1 = cc1 & 0x0F;
					cc2 = (cc2 >> 4) & 0x0F;

					for (i = 0; i < cnt; i++)
					{
						kbyte mask = 0x0F << shift;
						kbyte val = (kbyte)((shift == 0 ? cc1 : cc2) << shift);

						*dst = (*dst & (~mask)) | (val & mask);

						shift -= 4;

						if (shift < 0)
						{
							shift = 4;
							dst++;
						}
					}
				}
			}

			k_delete(data);

			return newdata;
		}


		K_OVR void Dispose()
		{
			delete this;
		}

		K_OVR bool Test(kconstpointer data, kint size)
		{
			kushort id = *(kushort*)data;

#if _SB_BIGENDIAN_
			id = K_ENDIAN_SWAP_USHORT(id);
#endif

			return id == 0x4D42;
		}

		K_OVR bool Read(kObj* ctnr, kconstpointer data, kint size, kint mask)
		{
			FileFmtBMP hdr;
			kint lpad;

			kint palsize;
			kint* paldata;

			kint pos;
			kbyte* buf;
			float f;
			kint wib, ldata;

			kbyte* bmp;

			// 헤더
			memcpy(&hdr, data, sizeof(FileFmtBMP));
			pos = sizeof(FileFmtBMP);

			if (hdr.Id != 0x4D42)
				return false;

			if (hdr.Compression > 2)
			{
				// 지원하지 않는 압축
				return false;
			}

			hdr.BitmapDataSize += (4 - (hdr.BitmapDataSize % 4)) % 4;

			// 컨테이너 검사
			qgImage* img = kCrbs::CastLinear<qgImage>(ctnr);

			if (img == NULL)
			{
				// 이거 이미지가 아님
				return false;
			}

			// 팔렛
			palsize = (hdr.BitmapDataOffset - pos) / 4;

			if (!palsize)
				paldata = NULL;
			else
			{
				paldata = k_new(palsize, kint);
				memcpy(paldata, (kbyte*)data + pos, palsize * sizeof(kint));
				pos += palsize * sizeof(kint);
			}

			// 읽기
			if (!hdr.BitmapDataSize)
			{
				// 크기 측정
				hdr.BitmapDataSize = size - hdr.BitmapDataOffset;
			}

			buf = (kbyte*)data + hdr.BitmapDataOffset;

			f = hdr.Width * (hdr.BPP / 8.0f);
			wib = (kint)f;
			f -= wib;

			if (!k_eq(f, 0.0f))
				wib++;

			ldata = wib + ((4 - (wib % 4))) % 4;
			lpad = ldata - wib;

			//
			bmp = k_new(hdr.BitmapDataSize, kbyte);
			memcpy(bmp, buf, hdr.BitmapDataSize);
			buf += hdr.BitmapDataSize;

			if (hdr.Compression == 1)
				bmp = Dec8BitRle(bmp, hdr.BitmapDataSize, hdr.Width, hdr.Height, lpad);
			else if (hdr.Compression == 2)
				bmp = Dec4BitRle(bmp, hdr.BitmapDataSize, hdr.Width, hdr.Height, lpad);
			else
			{
				// raw 그냥 그대로 씀
			}

			//
			if (K_OFLAG(mask, QGRESF_BGRA))
			{
				// 32비트 강제 변환
				img->Initialize(hdr.Width, hdr.Height, QGCF32_BGRA);

				switch (hdr.BPP)
				{
					case 1:
						qgBlitCopy::COPY_1_32(hdr.Width, hdr.Height, bmp, lpad, (kint*)img->GetData(), hdr.Width, true);
						break;

					case 4:
						qgBlitCopy::COPY_BGR_4_32(hdr.Width, hdr.Height, bmp, lpad, (kint*)img->GetData(), hdr.Width, paldata, true);
						break;

					case 8:
						qgBlitCopy::COPY_BGR_8_32(hdr.Width, hdr.Height, bmp, lpad, (kint*)img->GetData(), hdr.Width, paldata, true);
						break;

					case 16:
						qgBlitCopy::COPY_BGR_16_32(hdr.Width, hdr.Height, (kshort*)bmp, lpad, (kint*)img->GetData(), hdr.Width, true);
						break;

					case 24:
						qgBlitCopy::COPY_BGR_24_32(hdr.Width, hdr.Height, bmp, lpad, (kint*)img->GetData(), hdr.Width, true);
						break;

					case 32:
						qgBlitCopy::COPY_BGR_32_32(hdr.Width, hdr.Height, (kint*)bmp, lpad, (kint*)img->GetData(), hdr.Width, true);
						break;
				}
			}
			else
			{
				switch (hdr.BPP)
				{
					case 1:
						img->Initialize(hdr.Width, hdr.Height, QGCF16_RGBA);
						qgBlitCopy::COPY_1_16(hdr.Width, hdr.Height, bmp, lpad, (kushort*)img->GetData(), hdr.Width, true);
						break;

					case 4:
						img->Initialize(hdr.Width, hdr.Height, QGCF16_RGBA);
						qgBlitCopy::COPY_4_16(hdr.Width, hdr.Height, bmp, lpad, (kushort*)img->GetData(), hdr.Width, paldata, true);
						break;

					case 8:
						img->Initialize(hdr.Width, hdr.Height, QGCF16_RGBA);
						qgBlitCopy::COPY_8_16(hdr.Width, hdr.Height, bmp, lpad, (kshort*)img->GetData(), hdr.Width, paldata, true);
						break;

					case 16:
						img->Initialize(hdr.Width, hdr.Height, QGCF16_RGBA);
						qgBlitCopy::COPY_16_16(hdr.Width, hdr.Height, (kshort*)bmp, lpad, (kshort*)img->GetData(), hdr.Width, true);
						break;

					case 24:
						img->Initialize(hdr.Width, hdr.Height, QGCF32_RGBA);
						qgBlitCopy::COPY_24_32(hdr.Width, hdr.Height, bmp, lpad, (kint*)img->GetData(), hdr.Width, true);
						break;

					case 32:
						img->Initialize(hdr.Width, hdr.Height, QGCF32_RGBA);
						qgBlitCopy::COPY_32_32(hdr.Width, hdr.Height, (kint*)bmp, lpad, (kint*)img->GetData(), hdr.Width, true);
						break;
				}
			}

			k_delete(paldata);
			k_delete(bmp);

			return true;
		}
	};

	//
	class ImageWriterBmp : public qbFsWriter, public tpl::MemAlloc<>
	{
	public:
		K_OVR void Dispose()
		{
			delete this;
		}

		K_OVR bool Test(const char* ext)
		{
			return k_strieqv(ext, "bmp") != 0;
		}

		K_OVR bool Write(kStream* st, kObj* ctnr, kint mask)
		{
			// 컨테이너 검사
			qgImage* img = kCrbs::CastLinear<qgImage>(ctnr);

			if (img == NULL)
			{
				// 이거 이미지가 아님
				return false;
			}

			// 구성 검사
			const qbPixelProp& prop = img->GetPixelProp();

			if (prop.fmt != QGCF32_RGB && prop.fmt != QGCF32_RGBA &&
				prop.fmt != QGCF32_BGR && prop.fmt != QGCF32_BGRA)
				return false;

			kint pitch = img->GetPitch();
			kint width = img->GetWidth();
			kint height = img->GetHeight();
			kint outpitch = ((width * (24 / 8)) + 3) & ~3;
			kint size = outpitch * height;

			//
			FileFmtBMP hdr;
			hdr.Id = 0x4d42;
			hdr.FileSize = sizeof(FileFmtBMP) + size;
			hdr.Reserved = 0;
			hdr.BitmapDataOffset = sizeof(FileFmtBMP);
			hdr.BitmapHeaderSize = 0x28;
			hdr.Width = img->GetWidth();
			hdr.Height = img->GetHeight();
			hdr.Planes = 1;
			hdr.BPP = 24;
			hdr.Compression = 0;
			hdr.BitmapDataSize = size;
			hdr.PixelPerMeterX = 0;
			hdr.PixelPerMeterY = 0;
			hdr.Colors = 0;
			hdr.ImportantColors = 0;

			if (st->Write(&hdr, 0, sizeof(FileFmtBMP)) != sizeof(FileFmtBMP))
				return false;

			//
			kbyte* data = (kbyte*)img->GetData();
			kbyte* line = k_new_zero(outpitch, kbyte);
			kint x, y, t;

			if (prop.flags & QGCFF_BGR)
			{
				for (y = height - 1; y >= 0; --y)
				{
					kuint* p = (kuint*)(data + pitch*y);

					for (x = t = 0; x < width; x++, t += 3)
					{
						kvint& v = (kvint&)p[x];
						line[t + 0] = v.b.c;
						line[t + 1] = v.b.b;
						line[t + 2] = v.b.a;
					}

					if (st->Write(line, 0, outpitch) != outpitch)
						break;
				}
			}
			else
			{
				for (y = height - 1; y >= 0; --y)
				{
					kuint* p = (kuint*)(data + pitch*y);

					for (x = t = 0; x < width; x++, t += 3)
					{
						kvint& v = (kvint&)p[x];
						line[t + 0] = v.b.a;
						line[t + 1] = v.b.b;
						line[t + 2] = v.b.c;
					}

					if (st->Write(line, 0, outpitch) != outpitch)
						break;
				}
			}

			k_delete(line);

			return y < 0;
		}
	};

	//
	qbFsReader* CreateReaderImageBmp()
	{
		return new ImageReaderBmp();
	}

	//
	qbFsWriter* CreateWriterImageBmp()
	{
		return new ImageWriterBmp();
	}
}

#endif	// QG_NO_BMP
